home *** CD-ROM | disk | FTP | other *** search
- #!/usr/bin/python
- #
- # openssl-vulnkey: check a database of sha1'd static key hashes for
- # known vulnerable keys
- # Copyright (C) 2008 Canonical Ltd.
- # Author: Jamie Strandboge <jamie@canonical.com>
- #
- # This program is free software: you can redistribute it and/or modify
- # it under the terms of the GNU General Public License version 2,
- # as published by the Free Software Foundation.
- #
- # This program is distributed in the hope that it will be useful,
- # but WITHOUT ANY WARRANTY; without even the implied warranty of
- # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- # GNU General Public License for more details.
- #
- # You should have received a copy of the GNU General Public License
- # along with this program. If not, see <http://www.gnu.org/licenses/>.
- #
-
- from optparse import OptionParser
- import os
- import re
- import sha
- import subprocess
- import sys
- import tempfile
- import shutil
-
- version = "0.3.3"
- db_prefix = "/usr/share/openssl-blacklist/blacklist.RSA-"
- db_lines = []
-
- parser = OptionParser(usage="%prog FILE [FILE]", \
- version="%prog: " + version, \
- description="This program checks if FILEs are known " + \
- "vulnerable static keys")
- parser.add_option("-q", "--quiet", action="store_true", dest="quiet", \
- help="be quiet")
- parser.add_option("-b", "--bits", dest="bits", \
- help="number of bits (requires -m)")
- parser.add_option("-m", "--modulus", dest="modulus", \
- help="modulus to check (requires -b)")
- (options, args) = parser.parse_args()
-
- if not ((options.modulus and options.bits) or args):
- parser.print_help()
- sys.exit(1)
-
- def cmd(command, input = None, stderr = subprocess.PIPE, stdout = subprocess.PIPE, stdin = None):
- '''Try to execute given command (array) and return its stdout, or return
- a textual error if it failed.'''
-
- try:
- sp = subprocess.Popen(command, stdin=stdin, stdout=stdout, stderr=stderr, close_fds=True)
- except OSError, e:
- return [127, str(e)]
-
- out = sp.communicate(input)[0]
- return [sp.returncode,out]
-
- def get_contents(file):
- '''Determine the type of certificate it is. Returns empty string if
- unsupported.'''
- args = ['-modulus', '-text', '-in', file]
-
- rc, report = cmd(['openssl', 'rsa'] + args)
- if rc == 0:
- return ("rsa", report)
-
- rc, report = cmd(['openssl', 'x509'] + args)
- if rc == 0:
- return ("x509", report)
-
- rc, report = cmd(['openssl', 'req'] + args)
- if rc == 0:
- return ("req", report)
-
- return ("", report)
-
- def get_bits(contents, type):
- '''Find bit length of file. Returns empty string if unsupported.'''
- for line in contents:
- leading = "Private-Key: "
- if type == "x509" or type == "req":
- leading = "RSA Public Key: "
-
- # TODO: don't hardcode these
- if leading + "(512" in contents:
- return "512"
- elif leading + "(1024" in contents:
- return "1024"
- elif leading + "(2048" in contents:
- return "2048"
- elif leading + "(4096" in contents:
- return "4096"
- elif leading + "(8192" in contents:
- return "8192"
-
- return ""
-
- def get_modulus(contents):
- '''Find modulus of file'''
- for line in contents.split('\n'):
- if re.match(r'^Modulus=', line):
- return line + '\n'
-
- return ""
-
- def get_exponent(contents):
- '''Find exponent of file. Returns empty string if unsupported.'''
- if "Exponent: 65537 " in contents:
- return "65537"
-
- return ""
-
- def check_db(bits, last, modulus, name=""):
- '''Check modulus against database'''
- global db_lines
- if last != bits:
- db = db_prefix + bits
- # Read in the database
- try:
- fh = open(db, 'r')
- except:
- try:
- print >> sys.stderr, "WARN: could not open database for %s " \
- "bits. Skipped %s" % (bits, name)
- except IOError:
- pass
- return False
-
- db_lines = fh.read().split('\n')
- fh.close()
-
- key = sha.sha(modulus).hexdigest()
- #print "bits: %s\nmodulus: %s\nkey: %s\nkey80: %s" % (bits, modulus, key, key[20:])
- if key[20:] in db_lines:
- if not options.quiet:
- print "COMPROMISED: %s %s" % (key, name)
- return True
- else:
- if not options.quiet:
- print "Not blacklisted: %s %s" % (key, name)
- return False
-
-
- last_bits = ""
- found = False
-
- if options.bits and options.modulus:
- found = check_db(options.bits, last_bits, "Modulus=" + options.modulus + \
- "\n")
- else:
- # Check each file
- for f in args:
- realname = f
-
- if f == "-":
- # dump stdin to tmpfile, operate on tmpfile instead
- temp = tempfile.NamedTemporaryFile()
- shutil.copyfileobj(sys.stdin,temp)
- temp.flush()
- f = temp.name
-
- if not os.path.exists(f):
- if not options.quiet:
- print >> sys.stderr, "'%s' could not be opened (skipping)" % \
- (realname)
- continue
-
- (type, contents) = get_contents(f)
- if type == "":
- if not options.quiet:
- print >> sys.stderr, "'%s' is not x509, req or rsa (skipping)" \
- % (realname)
- continue
-
- exp = get_exponent(contents)
- if exp == "":
- if not options.quiet:
- print >> sys.stderr, "Unsupported exponent '%s' (skipping)" % \
- (realname)
- continue
-
- bits = get_bits(contents, type)
- if bits == "":
- if not options.quiet:
- print >> sys.stderr, "Key has unknown validity: %s" % \
- (realname)
- continue
-
- modulus = get_modulus(contents)
- if modulus == "":
- if not options.quiet:
- print >> sys.stderr, "Problem finding modulus: %s" % (realname)
- continue
-
- found = check_db(bits, last_bits, modulus, realname)
- last_bits = bits
-
- if found:
- sys.exit(1)
-
-